package org.example.testspring.context.impl;

import org.example.testspring.annotation.Autowired;
import org.example.testspring.annotation.Component;
import org.example.testspring.annotation.ComponentScan;
import org.example.testspring.annotation.Configuration;
import org.example.testspring.context.ApplicationContext;
import org.example.testspring.exception.NoNamedBeanException;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author 浆布
 */
public class MyAnnotationApplicationContext implements ApplicationContext {
    Map<String,Object> beans;
    public MyAnnotationApplicationContext(Class<?> clazz) throws NoNamedBeanException {
        beans = new HashMap<>();
        if (clazz.isAnnotationPresent(Configuration.class)) {
            Annotation[] annos =  clazz.getAnnotations();
            for (Annotation anno : annos) {
                if (anno instanceof ComponentScan) {
                    ComponentScan componentScan = (ComponentScan) anno;
                    String[] packages = componentScan.value();
                    for (String pack : packages) {
                        processPack(pack);
                    }
                }
            }
        }
        injectDependency();
    }

    private void injectDependency() throws NoNamedBeanException {
        for (Map.Entry<String,Object> entry : beans.entrySet()) {
            Object bean = entry.getValue();
            Class<?> clazz = bean.getClass();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    String beanName = field.getName();
                    field.setAccessible(true);
                    try {
                        Object dependency = beans.get(beanName);
                        if (dependency == null) {
                            throw new NoNamedBeanException("No bean named " + beanName);
                        }
                        field.set(bean,dependency);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    Set<Class<?>> getClassInPath(String path) {
        Reflections reflections = new Reflections(path);
        return reflections.getTypesAnnotatedWith(Component.class);
    }

    void processPack(String pack) {
        Set<Class<?>> clazzs = getClassInPath(pack);
        for (Class<?> clazz : clazzs) {
            if (clazz.isAnnotationPresent(Component.class)) {
                Component component = clazz.getAnnotation(Component.class);
                String beanName = component.value();
                Object instance = createInstanceFromClass(clazz);
                beans.put(beanName,instance);
            }
        }
    }

    Object createInstanceFromClass(Class<?> clazz){
        try {
            Constructor<?>[] constructors = clazz.getConstructors();
            for (Constructor<?> constructor : constructors) {
                if (constructor.getParameterCount() == 0) {
                    return constructor.newInstance();
                }
            }
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Object getBean(String beanName) throws NoNamedBeanException {
        if (beans.containsKey(beanName)) {
            return beans.get(beanName);
        } else {
            throw new NoNamedBeanException("No bean named " + beanName);
        }
    }

    @Override
    public void close() {
        beans.clear();
    }
}
